package guardian

import (
	"fmt"
	"github.com/Unixeno/islands/lode"
	"github.com/Unixeno/islands/pb"
	"github.com/opencontainers/runtime-spec/specs-go"
	"golang.org/x/sys/unix"
	"time"
)

func getContainerResources(config *pb.Resources) (*specs.LinuxResources, error) {
	if config == nil {
		return nil, nil
	}
	cpuResource, err := getCPUResources(config)
	if err != nil {
		return nil, err
	}
	readBpsDevice, err := getBlkioThrottleDevices(config.BlkioDeviceReadBps)
	if err != nil {
		return nil, err
	}
	writeBpsDevice, err := getBlkioThrottleDevices(config.BlkioDeviceWriteBps)
	if err != nil {
		return nil, err
	}
	readIOpsDevice, err := getBlkioThrottleDevices(config.BlkioDeviceReadIOps)
	if err != nil {
		return nil, err
	}
	writeIOpsDevice, err := getBlkioThrottleDevices(config.BlkioDeviceWriteIOps)
	if err != nil {
		return nil, err
	}
	weightDevices, err := getBlkioWeightDevices(config)
	if err != nil {
		return nil, err
	}
	memoryResources := getMemoryResources(config)
	pidLimits := getPidsLimit(config)
	blkioWeight := uint16(config.BlkioWeight)
	specResources := &specs.LinuxResources{
		Memory: memoryResources,
		CPU:    cpuResource,
		Pids:   pidLimits,
		BlockIO: &specs.LinuxBlockIO{
			Weight:                  &blkioWeight,
			WeightDevice:            weightDevices,
			ThrottleReadBpsDevice:   readBpsDevice,
			ThrottleWriteBpsDevice:  writeBpsDevice,
			ThrottleReadIOPSDevice:  readIOpsDevice,
			ThrottleWriteIOPSDevice: writeIOpsDevice,
		},
	}

	return specResources, nil
}

func getBlkioThrottleDevices(devs []*pb.ThrottleDevice) ([]specs.LinuxThrottleDevice, error) {
	var throttleDevices []specs.LinuxThrottleDevice
	var stat unix.Stat_t

	for _, d := range devs {
		if err := unix.Stat(d.Path, &stat); err != nil {
			return nil, err
		}
		d := specs.LinuxThrottleDevice{Rate: d.Rate}
		// the type is 32bit on mips
		d.Major = int64(unix.Major(uint64(stat.Rdev)))
		d.Minor = int64(unix.Minor(uint64(stat.Rdev)))
		throttleDevices = append(throttleDevices, d)
	}

	return throttleDevices, nil
}

func getMemoryResources(config *pb.Resources) *specs.LinuxMemory {
	memory := specs.LinuxMemory{}

	if config.Memory > 0 {
		memory.Limit = &config.Memory
	}

	if config.MemoryReservation > 0 {
		memory.Reservation = &config.MemoryReservation
	}

	if config.MemorySwap > 0 {
		memory.Swap = &config.MemorySwap
	}

	if config.MemorySwappiness != 0 {
		swappiness := uint64(config.MemorySwappiness)
		memory.Swappiness = &swappiness
	}

	if config.OomKillDisable != false {
		memory.DisableOOMKiller = &config.OomKillDisable
	}

	if config.KernelMemory != 0 {
		memory.Kernel = &config.KernelMemory
	}

	if config.KernelMemoryTCP != 0 {
		memory.KernelTCP = &config.KernelMemoryTCP
	}

	return &memory
}

func getPidsLimit(config *pb.Resources) *specs.LinuxPids {
	if config.PidsLimit == 0 {
		return nil
	}
	if config.PidsLimit <= 0 {
		// 可以使用负数
		// 但是底层 runtime tool 统一 -1
		return &specs.LinuxPids{Limit: -1}
	}
	return &specs.LinuxPids{Limit: config.PidsLimit}
}

func getCPUResources(config *pb.Resources) (*specs.LinuxCPU, error) {
	cpu := specs.LinuxCPU{}

	if config.CPUShares < 0 {
		return nil, fmt.Errorf("shares: invalid argument")
	}
	if config.CPUShares >= 0 {
		shares := uint64(config.CPUShares)
		cpu.Shares = &shares
	}

	if config.CpusetCpus != "" {
		cpu.Cpus = config.CpusetCpus
	}

	if config.CpusetMems != "" {
		cpu.Mems = config.CpusetMems
	}

	if config.NanoCPUs > 0 {
		// https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
		period := uint64(100 * time.Millisecond / time.Microsecond)
		quota := config.NanoCPUs * int64(period) / 1e9
		cpu.Period = &period
		cpu.Quota = &quota
	}

	if config.CPUPeriod != 0 {
		period := uint64(config.CPUPeriod)
		cpu.Period = &period
	}

	if config.CPUQuota != 0 {
		q := config.CPUQuota
		cpu.Quota = &q
	}

	if config.CPURealtimePeriod != 0 {
		period := uint64(config.CPURealtimePeriod)
		cpu.RealtimePeriod = &period
	}

	if config.CPURealtimeRuntime != 0 {
		c := config.CPURealtimeRuntime
		cpu.RealtimeRuntime = &c
	}

	return &cpu, nil
}

func getBlkioWeightDevices(config *pb.Resources) ([]specs.LinuxWeightDevice, error) {
	var stat unix.Stat_t
	var blkioWeightDevices []specs.LinuxWeightDevice

	for _, weightDevice := range config.BlkioWeightDevice {
		if err := unix.Stat(weightDevice.Path, &stat); err != nil {
			return nil, err
		}
		weight := uint16(weightDevice.Weight)
		d := specs.LinuxWeightDevice{Weight: &weight}
		// The type is 32bit on mips.
		d.Major = int64(unix.Major(uint64(stat.Rdev)))
		d.Minor = int64(unix.Minor(uint64(stat.Rdev)))
		blkioWeightDevices = append(blkioWeightDevices, d)
	}

	return blkioWeightDevices, nil
}

func getContainerNamespaces(config []*pb.Namespace) []specs.LinuxNamespace {
	namespaces := make([]specs.LinuxNamespace, 0, len(config))

	for _, ns := range config {
		namespaces = append(namespaces, specs.LinuxNamespace{
			Type: specs.LinuxNamespaceType(ns.Type),
			Path: ns.Path,
		})
	}

	return namespaces
}

func pbLayersToLayerInfos(pbLayers []*pb.LayerInfo) []lode.LayerInfo {
	layers := make([]lode.LayerInfo, len(pbLayers))

	for i, layer := range pbLayers {
		layers[i] = lode.LayerInfo{
			Digest:    layer.Digest,
			Size:      int(layer.Size),
			MediaType: layer.MediaType,
		}
	}

	return layers
}

func layerInfosToPbLayers(layerInfos []lode.LayerInfo) []*pb.LayerInfo {
	pbLayers := make([]*pb.LayerInfo, len(layerInfos))

	for i, layer := range layerInfos {
		pbLayers[i] = &pb.LayerInfo{
			Digest:    layer.Digest,
			Size:      int64(layer.Size),
			MediaType: layer.MediaType,
		}
	}

	return pbLayers
}
